﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;
using EffiProz.Core.Results;
using EffiProz.Core.Navigators;

namespace EffiProz.Core
{
    public class QuerySpecification : QueryExpression
    {
        private int resultRangePosition;
        public bool isDistinctSelect;
        public bool isAggregated;
        public bool isGrouped;
        public RangeVariable[] rangeVariables;
        private EfzArrayList rangeVariableList;
        public Expression queryCondition;
        public Expression checkQueryCondition;
        public Expression havingCondition;
        public Expression[] exprColumns;
        private EfzArrayList exprColumnList;
        public int indexLimitVisible;
        private int indexLimitRowId;
        private int groupByColumnCount;    // columns in 'group by'
        private int havingColumnCount;     // columns in 'having' (0 or 1)
        private int indexStartHaving;
        public int indexStartOrderBy;
        private int indexStartAggregates;
        private int indexLimitExpressions;
        private int indexLimitData;

        //
        public bool isUniqueResultRows;
        private bool simpleLimit;                 // true if maxrows can be uses as is
        private bool acceptsSequences;

        //
        SqlType[] columnTypes;
        private ArrayListIdentity aggregateSet;

        //
        private ArrayListIdentity resolvedSubqueryExpressions = null;

        //
        //
        private bool[] aggregateCheck;

        //
        private OrderedHashSet tempSet = new OrderedHashSet();

        //
        int[] columnMap;
        private Table baseTable;
        //private OrderedHashSet conditionTables;      // for view super-view references

        //
        public Index groupIndex;

        //
        public QuerySpecification(Session session, Table table,
                           ParserDML.CompileContext compileContext) : this(compileContext)
        {
          

            RangeVariable range = new RangeVariable(table, null, null, null,
                compileContext);

            range.addTableColumns(exprColumnList, 0, null);

            indexLimitVisible = exprColumnList.size();

            addRangeVariable(range);
            resolveReferences(session);
            resolveTypes(session);
            resolveTypes(session);

            sortAndSlice = SortAndSlice.noSort;
        }

        public QuerySpecification(ParserDML.CompileContext compileContext)
            : base(compileContext)
        {
         
            this.compileContext = compileContext;
            resultRangePosition = compileContext.getNextRangeVarIndex();
            rangeVariableList = new EfzArrayList();
            exprColumnList = new EfzArrayList();
            sortAndSlice = SortAndSlice.noSort;
        }

        public void addRangeVariable(RangeVariable rangeVar)
        {
            rangeVariableList.add(rangeVar);
        }

        private void finaliseRangeVariables()
        {

            if (rangeVariables == null
                    || rangeVariables.Length < rangeVariableList.size())
            {
                rangeVariables = new RangeVariable[rangeVariableList.size()];

                rangeVariableList.toArray(rangeVariables);
            }
        }

        public void addSelectColumnExpression(Expression e)
        {

            if (e.getType() == OpTypes.ROW)
            {
                throw Error.error(ErrorCode.X_42564);
            }

            exprColumnList.add(e);

            indexLimitVisible++;
        }

        public void addQueryCondition(Expression e)
        {
            queryCondition = e;
        }

        public void addGroupByColumnExpression(Expression e)
        {

            if (e.getType() == OpTypes.ROW)
            {
                throw Error.error(ErrorCode.X_42564);
            }

            exprColumnList.add(e);

            isGrouped = true;

            groupByColumnCount++;
        }

        public void addHavingExpression(Expression e)
        {

            exprColumnList.add(e);

            havingCondition = e;
            havingColumnCount = 1;
            isGrouped = true;
        }

        public override void addSortAndSlice(SortAndSlice sortAndSlice)
        {
            this.sortAndSlice = sortAndSlice;
        }

        public override void resolveReferences(Session session)
        {

            finaliseRangeVariables();
            resolveColumnReferencesForAsterisk();
            finaliseColumns();
            resolveColumnReferences();

            unionColumnTypes = new SqlType[indexLimitVisible];
            unionColumnMap = new int[indexLimitVisible];
            unionColumnNullability = new byte[indexLimitVisible];

            ArrayUtil.fillSequence(unionColumnMap);
        }

        /**
         * Resolves all column expressions in the GROUP BY clause and beyond.
         * Replaces any alias column expression in the ORDER BY cluase
         * with the actual select column expression.
         */
        private void resolveColumnReferences()
        {

            if (isDistinctSelect || isGrouped)
            {
                acceptsSequences = false;
            }

            for (int i = 0; i < rangeVariables.Length; i++)
            {
                Expression e = rangeVariables[i].nonIndexJoinCondition;

                if (e == null)
                {
                    continue;
                }

                resolveColumnReferencesAndAllocate(e, i + 1, false);
            }

            resolveColumnReferencesAndAllocate(queryCondition,
                                               rangeVariables.Length, false);

            if (resolvedSubqueryExpressions != null)
            {

                // subqueries in conditions not to be converted to SIMPLE_COLUMN
                resolvedSubqueryExpressions.setSize(0);
            }

            for (int i = 0; i < indexLimitVisible; i++)
            {
                resolveColumnReferencesAndAllocate(exprColumns[i],
                                                   rangeVariables.Length,
                                                   acceptsSequences);
            }

            for (int i = indexLimitVisible; i < indexStartHaving; i++)
            {
                resolveColumnReferencesAndAllocate(exprColumns[i],
                                                   rangeVariables.Length, false);
            }

            for (int i = indexStartHaving; i < indexStartOrderBy; i++)
            {
                resolveColumnReferencesAndAllocate(exprColumns[i],
                                                   rangeVariables.Length, false);
            }

            resolveColumnRefernecesInOrderBy(sortAndSlice);
        }

        public void resolveColumnRefernecesInOrderBy(SortAndSlice sortAndSlice)
        {

            // replace the aliases with expressions
            // replace column names with expressions and resolve the table columns
            int orderCount = sortAndSlice.getOrderLength();

            for (int i = 0; i < orderCount; i++)
            {
                ExpressionOrderBy e =
                    (ExpressionOrderBy)sortAndSlice.exprList.get(i);

                replaceColumnIndexInOrderBy(e);

                if (e.getLeftNode().queryTableColumnIndex != -1)
                {
                    continue;
                }

                if (sortAndSlice.sortUnion)
                {
                    if (e.getLeftNode().getType() != OpTypes.COLUMN)
                    {
                        throw Error.error(ErrorCode.X_42576);
                    }
                }

                e.replaceAliasInOrderBy(exprColumns, indexLimitVisible);
                resolveColumnReferencesAndAllocate(e, rangeVariables.Length,
                                                   false);
            }

            sortAndSlice.prepare(this);
        }

        private bool resolveColumnReferences(Expression e, int rangeCount,
                bool withSequences)
        {

            if (e == null)
            {
                return true;
            }

            int oldSize = unresolvedExpressions == null ? 0
                                                        : unresolvedExpressions
                                                            .size();

            unresolvedExpressions = e.resolveColumnReferences(rangeVariables,
                    rangeCount, unresolvedExpressions, withSequences);

            int newSize = unresolvedExpressions == null ? 0
                                                        : unresolvedExpressions
                                                            .size();

            return oldSize == newSize;
        }

        private void resolveColumnReferencesForAsterisk()
        {

            for (int pos = 0; pos < indexLimitVisible; )
            {
                Expression e = (Expression)(exprColumnList.get(pos));

                if (e.getType() == OpTypes.MULTICOLUMN)
                {
                    exprColumnList.remove(pos);

                    String tablename = ((ExpressionColumn)e).getTableName();

                    if (tablename == null)
                    {
                        addAllJoinedColumns(e);
                    }
                    else
                    {
                        int rangeIndex =
                            e.findMatchingRangeVariableIndex(rangeVariables);

                        if (rangeIndex == -1)
                        {
                            throw Error.error(ErrorCode.X_42501, tablename);
                        }

                        RangeVariable range = rangeVariables[rangeIndex];
                        HashSet exclude = getAllNamedJoinColumns();

                        range.addTableColumns(e, exclude);
                    }

                    for (int i = 0; i < e.nodes.Length; i++)
                    {
                        exprColumnList.add(pos, e.nodes[i]);

                        pos++;
                    }

                    indexLimitVisible += e.nodes.Length - 1;
                }
                else
                {
                    pos++;
                }
            }
        }

        private void resolveColumnReferencesAndAllocate(Expression expression,
                int count, bool withSequences)
        {

            if (expression == null)
            {
                return;
            }

            EfzList list = expression.resolveColumnReferences(rangeVariables,
                count, null, withSequences);

            if (list != null)
            {
                for (int i = 0; i < list.size(); i++)
                {
                    Expression e = (Expression)list.get(i);
                    bool resolved;

                    if (e.isSelfAggregate())
                    {
                        resolved = resolveColumnReferences(e.getLeftNode(), count,
                                                           false);
                    }
                    else
                    {
                        resolved = resolveColumnReferences(e, count,
                                                           withSequences);
                    }

                    if (resolved)
                    {
                        if (e.isSelfAggregate())
                        {
                            if (aggregateSet == null)
                            {
                                aggregateSet = new ArrayListIdentity();
                            }

                            aggregateSet.add(e);

                            isAggregated = true;
                            expression.isAggregate = true;
                        }

                        if (resolvedSubqueryExpressions == null)
                        {
                            resolvedSubqueryExpressions = new ArrayListIdentity();
                        }

                        resolvedSubqueryExpressions.add(e);
                    }
                    else
                    {
                        if (unresolvedExpressions == null)
                        {
                            unresolvedExpressions = new ArrayListIdentity();
                        }

                        unresolvedExpressions.add(e);
                    }
                }
            }
        }

        private HashSet getAllNamedJoinColumns()
        {

            HashSet set = null;

            for (int i = 0; i < rangeVariableList.size(); i++)
            {
                RangeVariable range = (RangeVariable)rangeVariableList.get(i);

                if (range.namedJoinColumns != null)
                {
                    if (set == null)
                    {
                        set = new HashSet();
                    }

                    set.addAll(range.namedJoinColumns);
                }
            }

            return set;
        }

        public Expression getEquiJoinExpressions(OrderedHashSet nameSet,
                RangeVariable rightRange, bool fullList)
        {

            HashSet set = new HashSet();
            Expression result = null;
            OrderedHashSet joinColumnNames = new OrderedHashSet();

            for (int i = 0; i < rangeVariableList.size(); i++)
            {
                RangeVariable range = (RangeVariable)rangeVariableList.get(i);
                HashMappedList columnList = range.rangeTable.columnList;

                for (int j = 0; j < columnList.size(); j++)
                {
                    ColumnSchema column = (ColumnSchema)columnList.get(j);
                    String name = range.getColumnAlias(j);
                    bool columnInList = nameSet.contains(name);
                    bool namedJoin = range.namedJoinColumns != null
                                        && range.namedJoinColumns.contains(name);
                    bool repeated = !namedJoin && !set.add(name);

                    if (repeated && (!fullList || columnInList))
                    {
                        throw Error.error(ErrorCode.X_42578, name);
                    }

                    if (!columnInList)
                    {
                        continue;
                    }

                    joinColumnNames.add(name);

                    int position = rightRange.rangeTable.getColumnIndex(name);
                    ColumnSchema rightColumn =
                        rightRange.rangeTable.getColumn(position);
                    Expression e = new ExpressionLogical(range, column,
                                                         rightRange, rightColumn);

                    result = ExpressionLogical.andExpressions(result, e);

                    ExpressionColumn col = range.getColumnExpression(name);

                    if (col == null)
                    {
                        col = new ExpressionColumn(new Expression[] {
                        e.getLeftNode(), e.getRightNode()
                    }, name);

                        range.addNamedJoinColumnExpression(name, col);
                    }
                    else
                    {
                        col.nodes = (Expression[])ArrayUtil.resizeArray(col.nodes,
                                col.nodes.Length + 1);
                        col.nodes[col.nodes.Length - 1] = e.getRightNode();
                    }

                    rightRange.addNamedJoinColumnExpression(name, col);
                }
            }

            if (fullList && !joinColumnNames.containsAll(nameSet))
            {
                throw Error.error(ErrorCode.X_42501);
            }

            rightRange.addNamedJoinColumns(joinColumnNames);

            return result;
        }

        private void addAllJoinedColumns(Expression e)
        {

            EfzArrayList list = new EfzArrayList();

            for (int i = 0; i < rangeVariables.Length; i++)
            {
                rangeVariables[i].addTableColumns(list);
            }

            Expression[] nodes = new Expression[list.size()];

            list.toArray(nodes);

            e.nodes = nodes;
        }

        private void finaliseColumns()
        {

            indexLimitRowId = indexLimitVisible;
            indexStartHaving = indexLimitRowId + groupByColumnCount;
            indexStartOrderBy = indexStartHaving + havingColumnCount;
            indexStartAggregates = indexStartOrderBy
                                   + sortAndSlice.getOrderLength();
            indexLimitData = indexLimitExpressions = indexStartAggregates;
            exprColumns = new Expression[indexLimitExpressions];

            exprColumnList.toArray(exprColumns);

            for (int i = 0; i < indexLimitVisible; i++)
            {
                exprColumns[i].queryTableColumnIndex = i;
            }

            if (sortAndSlice.hasOrder())
            {
                for (int i = 0; i < sortAndSlice.getOrderLength(); i++)
                {
                    exprColumns[indexStartOrderBy + i] =
                        (Expression)sortAndSlice.exprList.get(i);
                }
            }
        }

        private void replaceColumnIndexInOrderBy(Expression orderBy)
        {

            Expression e = orderBy.getLeftNode();

            if (e.getType() != OpTypes.VALUE)
            {
                return;
            }

            if (e.getDataType().typeCode == Types.SQL_INTEGER)
            {
                int i = ((int)e.getValue(null));

                if (0 < i && i <= indexLimitVisible)
                {
                    orderBy.setLeftNode(exprColumns[i - 1]);

                    return;
                }
            }

            throw Error.error(ErrorCode.X_42576);
        }

        public void collectRangeVariables(RangeVariable[] rangeVars, Set set)
        {

            for (int i = 0; i < indexStartAggregates; i++)
            {
                exprColumns[i].collectRangeVariables(rangeVars, set);
            }

            if (queryCondition != null)
            {
                queryCondition.collectRangeVariables(rangeVars, set);
            }

            if (havingCondition != null)
            {
                havingCondition.collectRangeVariables(rangeVars, set);
            }
        }

        public override bool hasReference(RangeVariable range)
        {

            if (unresolvedExpressions == null)
            {
                return false;
            }

            for (int i = 0; i < unresolvedExpressions.size(); i++)
            {
                if (((Expression)unresolvedExpressions.get(i)).hasReference(
                        range))
                {
                    return true;
                }
            }

            return false;
        }

        /**
         * Sets the types of all the expressions used in this SELECT list.
         */
        public void resolveExpressionTypes(Session session)
        {

            for (int i = 0; i < indexStartAggregates; i++)
            {
                Expression e = exprColumns[i];

                e.resolveTypes(session, null);

                if (e.getType() == OpTypes.ROW)
                {
                    throw Error.error(ErrorCode.X_42564);
                }
            }

            for (int i = 0, len = rangeVariables.Length; i < len; i++)
            {
                Expression e = rangeVariables[i].nonIndexJoinCondition;

                if (e != null)
                {
                    e.resolveTypes(session, null);

                    if (e.getDataType() != SqlType.SQL_BOOLEAN)
                    {
                        throw Error.error(ErrorCode.X_42568);
                    }
                }
            }

            if (queryCondition != null)
            {
                queryCondition.resolveTypes(session, null);

                if (queryCondition.getDataType() != SqlType.SQL_BOOLEAN)
                {
                    throw Error.error(ErrorCode.X_42568);
                }
            }

            if (havingCondition != null)
            {
                havingCondition.resolveTypes(session, null);

                if (havingCondition.getDataType() != SqlType.SQL_BOOLEAN)
                {
                    throw Error.error(ErrorCode.X_42568);
                }
            }
        }

        private void resolveAggregates()
        {

            tempSet.clear();

            if (isAggregated)
            {
                aggregateCheck = new bool[indexStartAggregates];

                tempSet.addAll(aggregateSet);

                indexLimitData = indexLimitExpressions = exprColumns.Length
                        + tempSet.size();
                exprColumns = (Expression[])ArrayUtil.resizeArray(exprColumns,
                        indexLimitExpressions);

                for (int i = indexStartAggregates, j = 0;
                        i < indexLimitExpressions; i++, j++)
                {
                    ExpressionAggregate e = (ExpressionAggregate)tempSet.get(j);

                    exprColumns[i] = new ExpressionAggregate(e);
                    exprColumns[i].dataType = e.dataType;
                }

                tempSet.clear();
            }
        }

        public override bool areColumnsResolved()
        {
            return unresolvedExpressions == null
                   || unresolvedExpressions.isEmpty();
        }

        private void setRangeVariableConditions()
        {

            RangeVariableResolver rangeResolver =
                new RangeVariableResolver(rangeVariables, queryCondition,
                                          compileContext);

            rangeResolver.processConditions();

            rangeVariables = rangeResolver.rangeVariables;

            //        queryCondition = null;
        }

        public override void resolveTypes(Session session)
        {

            if (isResolved)
            {
                return;
            }

            resolveTypesPartOne(session);
            resolveTypesPartTwo(session);
            Array.Copy(resultTable.colTypes, unionColumnTypes,
                                unionColumnTypes.Length);

            for (int j = 0; j < resultTable.colNotNull.Length; j++)
            {
                resultTable.colNotNull[j] = unionColumnNullability[j] == SchemaObjectNullability.NO_NULLS;
            }

            for (int i = 0; i < indexStartHaving; i++)
            {
                if (exprColumns[i].dataType == null)
                {
                    throw Error.error(ErrorCode.X_42567);
                }
            }

            isResolved = true;

            return;
        }

        public byte getAggregateNullability(int lNullability, int rNullability)
        {
            if (lNullability == SchemaObjectNullability.NO_NULLS
                && rNullability == SchemaObjectNullability.NO_NULLS)
                return SchemaObjectNullability.NO_NULLS;
            else if (lNullability == SchemaObjectNullability.NULLABLE_UNKNOWN ||
                rNullability == SchemaObjectNullability.NULLABLE_UNKNOWN)
                return SchemaObjectNullability.NULLABLE_UNKNOWN;
            else
                return SchemaObjectNullability.NULLABLE;
        }

        public override void resolveTypesPartOne(Session session)
        {

            resolveExpressionTypes(session);
            setRangeVariableConditions();
            resolveAggregates();

            for (int i = 0; i < unionColumnMap.Length; i++)
            {
                unionColumnTypes[i] = SqlType.getAggregateType(unionColumnTypes[i],
                        exprColumns[i].getDataType());
               
                unionColumnNullability[i] = getAggregateNullability(unionColumnNullability[i],
                        exprColumns[i].getNullability());
                
            }
        }

        public override void resolveTypesPartTwo(Session session)
        {

            resolveGroups();

            for (int i = 0; i < unionColumnMap.Length; i++)
            {
                SqlType type = unionColumnTypes[unionColumnMap[i]];
                byte nullability = unionColumnNullability[unionColumnMap[i]];

                if (type == null)
                {
                    throw Error.error(ErrorCode.X_42567);
                }

                exprColumns[unionColumnMap[i]].setDataType(session, type);
                exprColumns[unionColumnMap[i]].setNullability(nullability);
            }

            for (int i = 0; i < indexStartHaving; i++)
            {
                if (exprColumns[i].dataType == null)
                {
                    throw Error.error(ErrorCode.X_42567);
                }
            }

            setReferenceableColumns();
            setUpdatability();
            createResultMetaData();
            createTable(session);

            if (isUpdatable())
            {
                getMergedSelect();
            }
        }

        private void resolveGroups()
        {

            // - 1.9.0 is standard compliant but has more extended support for
            //   referencing columns
            // - check there is no direct aggregate expression in group by
            // - check each expression in select list can be
            //   decomposed into the expressions in group by or any aggregates
            //   this allows direct function of group by expressions, but
            //   doesn't allow indirect functions. e.g.
            //     select 2*abs(cola) , sum(colb) from t group by abs(cola) // ok
            //     select 2*(cola + 10) , sum(colb) from t group by cola + 10 // ok
            //     select abs(cola) , sum(colb) from t group by cola // ok
            //     select 2*cola + 20 , sum(colb) from t group by cola + 10 // not allowed although correct
            //     select cola , sum(colb) from t group by abs(cola) // not allowed because incorrect
            // - group by can introduce invisible, derived columns into the query table
            // - check the having expression can be decomposed into
            //   select list expresions plus group by expressions
            // - having cannot introduce additional, derived columns
            // - having cannot reference columns not in the select or group by list
            // - if there is any aggregate in select list but no group by, no
            //   non-aggregates is allowed
            // - check order by columns
            // - if distinct select, order by must be composed of the select list columns
            // - if grouped by, then order by should be decomposed into the
            //   select list plus group by list
            // - references to column aliases are allowed only in order by (Standard
            //   compliance) and take precendence over references to non-alias
            //   column names.
            // - references to table / correlation and column list in correlation
            //   names are handled according to the Standard
            //  fredt@users
            tempSet.clear();

            if (isGrouped)
            {
                for (int i = indexLimitVisible;
                        i < indexLimitVisible + groupByColumnCount; i++)
                {
                    Expression.collectAllExpressions(
                        tempSet, exprColumns[i], Expression.aggregateFunctionSet,
                        Expression.subqueryExpressionSet);

                    if (!tempSet.isEmpty())
                    {
                        throw Error.error(ErrorCode.X_42572,
                                          ((Expression)tempSet.get(0)).getSQL());
                    }
                }

                for (int i = 0; i < indexLimitVisible; i++)
                {
                    if (!exprColumns[i].isComposedOf(
                            exprColumns, indexLimitVisible,
                            indexLimitVisible + groupByColumnCount,
                            Expression.subqueryAggregateExpressionSet))
                    {
                        tempSet.add(exprColumns[i]);
                    }
                }

                if (!tempSet.isEmpty() && !resolveForGroupBy(tempSet))
                {
                    throw Error.error(ErrorCode.X_42574,
                                      ((Expression)tempSet.get(0)).getSQL());
                }
            }
            else if (isAggregated)
            {
                for (int i = 0; i < indexLimitVisible; i++)
                {
                    Expression.collectAllExpressions(
                        tempSet, exprColumns[i], Expression.columnExpressionSet,
                        Expression.aggregateFunctionSet);

                    if (!tempSet.isEmpty())
                    {
                        throw Error.error(ErrorCode.X_42574,
                                          ((Expression)tempSet.get(0)).getSQL());
                    }
                }
            }

            tempSet.clear();

            if (havingCondition != null)
            {
                if (unresolvedExpressions != null)
                {
                    tempSet.addAll(unresolvedExpressions);
                }

                for (int i = indexLimitVisible;
                        i < indexLimitVisible + groupByColumnCount; i++)
                {
                    tempSet.add(exprColumns[i]);
                }

                if (!havingCondition.isComposedOf(
                        tempSet, Expression.subqueryAggregateExpressionSet))
                {
                    throw Error.error(ErrorCode.X_42573);
                }

                tempSet.clear();
            }

            if (isDistinctSelect)
            {
                int orderCount = sortAndSlice.getOrderLength();

                for (int i = 0; i < orderCount; i++)
                {
                    Expression e = (Expression)sortAndSlice.exprList.get(i);

                    if (e.queryTableColumnIndex != -1)
                    {
                        continue;
                    }

                    if (!e.isComposedOf(exprColumns, 0, indexLimitVisible,
                                        Expression.emptyExpressionSet))
                    {
                        throw Error.error(ErrorCode.X_42576);
                    }
                }
            }

            if (isGrouped)
            {
                int orderCount = sortAndSlice.getOrderLength();

                for (int i = 0; i < orderCount; i++)
                {
                    Expression e = (Expression)sortAndSlice.exprList.get(i);

                    if (e.queryTableColumnIndex != -1)
                    {
                        continue;
                    }

                    if (!e.isComposedOf(exprColumns, 0,
                                        indexLimitVisible + groupByColumnCount,
                                        Expression.emptyExpressionSet))
                    {
                        throw Error.error(ErrorCode.X_42576);
                    }
                }
            }

            simpleLimit = (!isDistinctSelect && !isGrouped
                           && !sortAndSlice.hasOrder());

            if (!isAggregated)
            {
                return;
            }

            OrderedHashSet expressions = new OrderedHashSet();
            OrderedHashSet columnExpressions = new OrderedHashSet();

            for (int i = indexStartAggregates; i < indexLimitExpressions; i++)
            {
                Expression e = exprColumns[i];
                Expression c = new ExpressionColumn(e, i, resultRangePosition);

                expressions.add(e);
                columnExpressions.add(c);
            }

            for (int i = 0; i < indexStartHaving; i++)
            {
                if (exprColumns[i].isAggregate)
                {
                    continue;
                }

                Expression e = exprColumns[i];

                if (expressions.add(e))
                {
                    Expression c = new ExpressionColumn(e, i, resultRangePosition);

                    columnExpressions.add(c);
                }
            }

            // order by with aggregate
            int orderCount1 = sortAndSlice.getOrderLength();

            for (int i = 0; i < orderCount1; i++)
            {
                Expression e = (Expression)sortAndSlice.exprList.get(i);

                if (e.getLeftNode().isAggregate)
                {
                    e.isAggregate = true;
                }
            }

            for (int i = indexStartOrderBy; i < indexStartAggregates; i++)
            {
                if (exprColumns[i].getLeftNode().isAggregate)
                {
                    exprColumns[i].isAggregate = true;
                }
            }

            for (int i = 0; i < indexStartAggregates; i++)
            {
                Expression e = exprColumns[i];

                if (!e.isAggregate)
                {
                    continue;
                }

                aggregateCheck[i] = true;

                e.convertToSimpleColumn(expressions, columnExpressions);
            }

            for (int i = 0; i < aggregateSet.size(); i++)
            {
                Expression e = (Expression)aggregateSet.get(i);

                e.convertToSimpleColumn(expressions, columnExpressions);
            }

            if (resolvedSubqueryExpressions != null)
            {
                for (int i = 0; i < resolvedSubqueryExpressions.size(); i++)
                {
                    Expression e = (Expression)resolvedSubqueryExpressions.get(i);

                    e.convertToSimpleColumn(expressions, columnExpressions);
                }
            }
        }

        public bool resolveForGroupBy(EfzList unresolvedSet)
        {

            for (int i = indexLimitVisible;
                    i < indexLimitVisible + groupByColumnCount; i++)
            {
                Expression e = exprColumns[i];

                if (e.getType() == OpTypes.COLUMN)
                {
                    RangeVariable range = e.getRangeVariable();
                    int colIndex = e.getColumnIndex();

                    range.columnsInGroupBy[colIndex] = true;
                }
            }

            for (int i = 0; i < rangeVariables.Length; i++)
            {
                RangeVariable range = rangeVariables[i];

                range.hasKeyedColumnInGroupBy =
                    range.rangeTable.getUniqueNotNullColumnGroup(
                        range.columnsInGroupBy) != null;
            }

            OrderedHashSet set = null;

            for (int i = 0; i < unresolvedSet.size(); i++)
            {
                Expression e = (Expression)unresolvedSet.get(i);

                set = e.getUnkeyedColumns(set);
            }

            return set == null;
        }

        private int getLimitStart(Session session)
        {

            if (sortAndSlice.limitCondition != null)
            {
                int? limit = (int?)sortAndSlice.limitCondition.getLeftNode().getValue(
                        session);
                 

                if (limit == null || limit < 0)
                {
                    throw Error.error(ErrorCode.X_2201X);
                }

                return (int)limit;
            }

            return 0;
        }

        private int getLimitCount(Session session, int rowCount)
        {

            int limitCount = int.MaxValue;

            if (sortAndSlice.limitCondition != null)
            {
                int? limit = (int?)   sortAndSlice.limitCondition.getRightNode().getValue(
                        session);

                if (limit == null || limit <= 0)
                {
                    throw Error.error(ErrorCode.X_2201W);
                }

                limitCount = (int)limit;
            }

            if (rowCount != 0 && rowCount < limitCount)
            {
                limitCount = rowCount;
            }

            return limitCount;
        }

        /**
         * translate the rowCount into total number of rows needed from query,
         * including any rows skipped at the beginning
         */
        private int getMaxRowCount(Session session, int rowCount)
        {

            int limitStart = getLimitStart(session);
            int limitCount = getLimitCount(session, rowCount);

            if (simpleLimit)
            {
                if (rowCount == 0)
                {
                    rowCount = limitCount;
                }

                if (rowCount == 0 || rowCount > int.MaxValue - limitStart)
                {
                    rowCount = int.MaxValue;
                }
                else
                {
                    rowCount += limitStart;
                }
            }
            else
            {
                rowCount = int.MaxValue;
            }

            return rowCount;
        }

        /**
         * Returns the result of executing this Select.
         *
         * @param maxrows may be 0 to indicate no limit on the number of rows.
         * Positive values limit the size of the result set.
         * @return the result of executing this Select
         */
        public override Result getResult(Session session, int maxrows)
        {

            Result r;

            r = getSingleResult(session, maxrows);

            // fredt - now there is no need for the sort and group columns
            //        r.setColumnCount(indexLimitVisible);
            r.getNavigator().reset();

            return r;
        }

        private Result getSingleResult(Session session, int rowCount)
        {

            int maxRows = getMaxRowCount(session, rowCount);
            Result r = buildResult(session, maxRows);
            RowSetNavigatorData navigator = (RowSetNavigatorData)r.getNavigator();

            if (isDistinctSelect)
            {
                navigator.removeDuplicates();
            }

            navigator.sortOrder();
            navigator.trim(getLimitStart(session),
                           getLimitCount(session, rowCount));

            return r;
        }

        private Result buildResult(Session session, int limitcount)
        {

            RowSetNavigatorData navigator = new RowSetNavigatorData(session,
                (QuerySpecification)this);
            Result result = Result.newResult(navigator);

            result.metaData = resultMetaData;

            result.setDataResultConcurrency(_isUpdatable);

            int fullJoinIndex = 0;
            RangeIterator[] rangeIterators =
                new RangeIterator[rangeVariables.Length];

            for (int i = 0; i < rangeVariables.Length; i++)
            {
                rangeIterators[i] = rangeVariables[i].getIterator(session);
            }

            for (int currentIndex = 0; ; )
            {
                if (currentIndex < fullJoinIndex)
                {
                    bool end = true;

                    for (int i = fullJoinIndex + 1; i < rangeVariables.Length;
                            i++)
                    {
                        if (rangeVariables[i].isRightJoin)
                        {
                            rangeIterators[i] = rangeVariables[i].getFullIterator(
                                session, (RangeVariable.RangeIteratorMain)rangeIterators[i]);
                            fullJoinIndex = i;
                            currentIndex = i;
                            end = false;

                            break;
                        }
                    }

                    if (end)
                    {
                        break;
                    }
                }

                RangeIterator it = rangeIterators[currentIndex];

                if (it.next())
                {
                    if (currentIndex < rangeVariables.Length - 1)
                    {
                        currentIndex++;

                        continue;
                    }
                }
                else
                {
                    it.reset();

                    currentIndex--;

                    continue;
                }

                session.sessionData.startRowProcessing();

                Object[] data = new Object[indexLimitData];

                for (int i = 0; i < indexStartAggregates; i++)
                {
                    if (isAggregated && aggregateCheck[i])
                    {
                        continue;
                    }
                    else
                    {
                        data[i] = exprColumns[i].getValue(session);
                    }
                }

                for (int i = indexLimitVisible; i < indexLimitRowId; i++)
                {
                    data[i] = it.getRowidObject();
                }

                Object[] groupData = null;

                if (isAggregated || isGrouped)
                {
                    groupData = navigator.getGroupData(data);

                    if (groupData != null)
                    {
                        data = groupData;
                    }
                }

                for (int i = indexStartAggregates; i < indexLimitExpressions;
                        i++)
                {
                    data[i] =
                        ((ExpressionAggregate)exprColumns[i])
                            .updateAggregatingValue(session, data[i]);
                }

                if (groupData == null)
                {
                    navigator.add(data);
                }

                if (isAggregated || isGrouped)
                {
                    continue;
                }

                if (navigator.getSize() >= limitcount)
                {
                    break;
                }
            }

            navigator.reset();

            if (!isGrouped && !isAggregated)
            {
                return result;
            }

            if (isAggregated)
            {
                if (!isGrouped && navigator.getSize() == 0)
                {
                    Object[] data = new Object[exprColumns.Length];

                    navigator.add(data);
                }

                RangeVariable.RangeIteratorBase it = new RangeVariable.RangeIteratorBase(session,
                    navigator.store, navigator.table, resultRangePosition);

                session.sessionContext.setRangeIterator(it);

                while (it.next())
                {
                    for (int i = indexStartAggregates; i < indexLimitExpressions;
                            i++)
                    {
                        ExpressionAggregate aggregate =
                            (ExpressionAggregate)exprColumns[i];

                        it.currentData[i] = aggregate.getAggregatedValue(session,
                                it.currentData[i]);
                    }

                    for (int i = 0; i < indexStartAggregates; i++)
                    {
                        if (aggregateCheck[i])
                        {
                            it.currentData[i] = exprColumns[i].getValue(session);
                        }
                    }
                }
            }

            navigator.reset();

            if (havingCondition != null)
            {
                while (navigator.hasNext())
                {
                    Object[] data = (Object[])navigator.getNext();

                    if (!true.Equals(
                            data[indexLimitVisible + groupByColumnCount]))
                    {
                        navigator.remove();
                    }
                }

                navigator.reset();
            }

            return result;
        }

        public void setReferenceableColumns()
        {

            accessibleColumns = new bool[indexLimitVisible];

            IntValueHashMap aliases = new IntValueHashMap();

            for (int i = 0; i < indexLimitVisible; i++)
            {
                Expression expression = exprColumns[i];
                String alias = expression.getAlias();

                if (alias.Length == 0)
                {
                    QNameManager.SimpleName name = QNameManager.getAutoColumnName(i);

                    expression.setAlias(name);

                    continue;
                }

                int index = aliases.get(alias, -1);

                if (index == -1)
                {
                    aliases.put(alias, i);

                    accessibleColumns[i] = true;
                }
                else
                {
                    accessibleColumns[index] = false;
                }
            }
        }

        private void createResultMetaData()
        {

            columnTypes = new SqlType[indexLimitData];

            for (int i = 0; i < indexStartAggregates; i++)
            {
                Expression e = exprColumns[i];

                columnTypes[i] = e.getDataType();
            }

            for (int i = indexLimitVisible; i < indexLimitRowId; i++)
            {
                columnTypes[i] = SqlType.SQL_BIGINT;
            }

            for (int i = indexLimitRowId; i < indexLimitData; i++)
            {
                Expression e = exprColumns[i];

                columnTypes[i] = e.getDataType();
            }

            resultMetaData = ResultMetaData.newResultMetaData(columnTypes,
                    columnMap, indexLimitVisible, indexLimitRowId);

            for (int i = 0; i < indexLimitVisible; i++)
            {
                Expression e = exprColumns[i];

                resultMetaData.columnTypes[i] = e.getDataType();

                if (i < indexLimitVisible)
                {
                    ColumnBase column = e.getColumn();

                    if (column != null)
                    {
                        resultMetaData.columns[i] = column;
                        resultMetaData.columnLabels[i] = e.getOriginalAlias();

                        continue;
                    }

                    column = new ColumnBase();

                    column.setType(e.getDataType());

                    resultMetaData.columns[i] = column;
                    resultMetaData.columnLabels[i] = e.getOriginalAlias();
                }
            }
        }

       public override void createTable(Session session)
        {

            createResultTable(session);

            mainIndex = resultTable.getPrimaryIndex();

            if (sortAndSlice.hasOrder())
            {
                orderIndex = resultTable.createAndAddIndexStructure(null,
                        sortAndSlice.sortOrder, sortAndSlice.sortDescending,
                        sortAndSlice.sortNullsLast, false, false, false);
            }

            if (isDistinctSelect || isFullOrder)
            {
                int[] fullCols = new int[indexLimitVisible];

                ArrayUtil.fillSequence(fullCols);

                fullIndex = resultTable.createAndAddIndexStructure(null, fullCols,
                        null, null, false, false, false);
                resultTable.fullIndex = fullIndex;
            }

            if (isGrouped)
            {
                int[] groupCols = new int[groupByColumnCount];

                for (int i = 0; i < groupByColumnCount; i++)
                {
                    groupCols[i] = indexLimitVisible + i;
                }

                groupIndex = resultTable.createAndAddIndexStructure(null,
                        groupCols, null, null, false, false, false);
            }
            else if (isAggregated)
            {
                groupIndex = mainIndex;
            }
        }

        public override void createResultTable(Session session)
        {

            QNameManager.QName tableName;
            HashMappedList columnList;
            int tableType;

            tableName = session.database.nameManager.getSubqueryTableName();
            tableType = persistenceScope == TableBase.SCOPE_STATEMENT
                        ? TableBase.SYSTEM_SUBQUERY
                        : TableBase.RESULT_TABLE;
            columnList = new HashMappedList();

            byte[] columnNullabiliy = new byte[indexLimitVisible];

            for (int i = 0; i < indexLimitVisible; i++)
            {
                Expression e = exprColumns[i];
                QNameManager.SimpleName simpleName = e.getSimpleName();
                String nameString = simpleName.name;
                QNameManager.QName name =
                    session.database.nameManager.newColumnSchemaHsqlName(tableName,
                        simpleName);

                if (!accessibleColumns[i])
                {
                    nameString = QNameManager.getAutoNoNameColumnString(i);
                }

                columnNullabiliy[i] = e.getNullability();
                ColumnSchema column = new ColumnSchema(name, e.dataType, 
                    e.getNullability() != SchemaObjectNullability.NO_NULLS,
                                                       false, null);

                columnList.add(nameString, column);
            }

            try
            {
                resultTable = new TableDerived(session.database, tableName,
                                               tableType, columnTypes,columnNullabiliy, columnList,
                                               null);
            }
            catch (Exception) { }
        }

        public String getSQL()
        {

            StringBuilder sb = new StringBuilder();
            int limit;

            sb.Append(Tokens.T_SELECT).Append(' ');

            limit = indexLimitVisible;

            for (int i = 0; i < limit; i++)
            {
                if (i > 0)
                {
                    sb.Append(',');
                }

                sb.Append(exprColumns[i].getSQL());
            }

            sb.Append(Tokens.T_FROM);

            limit = rangeVariables.Length;

            for (int i = 0; i < limit; i++)
            {
                RangeVariable rangeVar = rangeVariables[i];

                if (i > 0)
                {
                    if (rangeVar.isLeftJoin && rangeVar.isRightJoin)
                    {
                        sb.Append(Tokens.T_FULL).Append(' ');
                    }
                    else if (rangeVar.isLeftJoin)
                    {
                        sb.Append(Tokens.T_LEFT).Append(' ');
                    }
                    else if (rangeVar.isRightJoin)
                    {
                        sb.Append(Tokens.T_RIGHT).Append(' ');
                    }

                    sb.Append(Tokens.T_JOIN).Append(' ');
                }

                sb.Append(rangeVar.getTable().getName().statementName);
            }

            if (isGrouped)
            {
                sb.Append(' ').Append(Tokens.T_GROUP).Append(' ').Append(
                    Tokens.T_BY);

                limit = indexLimitVisible + groupByColumnCount;

                for (int i = indexLimitVisible; i < limit; i++)
                {
                    sb.Append(exprColumns[i].getSQL());

                    if (i < limit - 1)
                    {
                        sb.Append(',');
                    }
                }
            }

            if (havingCondition != null)
            {
                sb.Append(' ').Append(Tokens.T_HAVING).Append(' ');
                sb.Append(havingCondition.getSQL());
            }

            if (sortAndSlice.hasOrder())
            {
                limit = indexStartOrderBy + sortAndSlice.getOrderLength();

                sb.Append(' ').Append(Tokens.T_ORDER).Append(Tokens.T_BY).Append(
                    ' ');

                for (int i = indexStartOrderBy; i < limit; i++)
                {
                    sb.Append(exprColumns[i].getSQL());

                    if (i < limit - 1)
                    {
                        sb.Append(',');
                    }
                }
            }

            if (sortAndSlice.hasLimit())
            {
                sb.Append(sortAndSlice.limitCondition.getLeftNode().getSQL());
            }

            return sb.ToString();
        }

        public override ResultMetaData getMetaData()
        {
            return resultMetaData;
        }

        public override String describe(Session session)
        {

            StringBuilder sb;
            String temp;

            /*
                    // temporary :  it is currently unclear whether this may affect
                    // later attempts to retrieve an actual result (calls getResult(1)
                    // in preProcess mode).  Thus, toString() probably should not be called
                    // on Select objects that will actually be used to retrieve results,
                    // only on Select objects used by EXPLAIN PLAN FOR
                    try {
                        getResult(session, 1);
                    } catch (CoreException e) {}
            */
            sb = new StringBuilder();

            sb.Append(base.ToString()).Append("[\n");

            if (sortAndSlice.limitCondition != null)
            {
                sb.Append("offset=[").Append(
                    sortAndSlice.limitCondition.getLeftNode().describe(
                        session)).Append("]\n");
                sb.Append("limit=[").Append(
                    sortAndSlice.limitCondition.getRightNode().describe(
                        session)).Append("]\n");
            }

            sb.Append("isDistinctSelect=[").Append(isDistinctSelect).Append("]\n");
            sb.Append("isGrouped=[").Append(isGrouped).Append("]\n");
            sb.Append("isAggregated=[").Append(isAggregated).Append("]\n");
            sb.Append("columns=[");

            for (int i = 0; i < indexLimitVisible; i++)
            {
                int index = i;

                if (exprColumns[i].getType() == OpTypes.SIMPLE_COLUMN)
                {
                    index = exprColumns[i].columnIndex;
                }

                sb.Append(exprColumns[index].describe(session));
            }

            sb.Append("\n]\n");
            sb.Append("range variables=[\n");

            for (int i = 0; i < rangeVariables.Length; i++)
            {
                sb.Append("[\n");
                sb.Append(rangeVariables[i].describe(session));
                sb.Append("\n]");
            }

            sb.Append("]\n");

            temp = queryCondition == null ? "null"
                                          : queryCondition.describe(session);

            sb.Append("queryCondition=[").Append(temp).Append("]\n");
            sb.Append("groupColumns=[");

            for (int i = indexLimitRowId; i < indexLimitRowId + groupByColumnCount;
                    i++)
            {
                int index = i;

                if (exprColumns[i].getType() == OpTypes.SIMPLE_COLUMN)
                {
                    index = exprColumns[i].columnIndex;
                }

                sb.Append(exprColumns[index].describe(session));
            }

            sb.Append("]\n");

            temp = havingCondition == null ? "null"
                                           : havingCondition.describe(session);

            sb.Append("havingCondition=[").Append(temp).Append("]\n");

            return sb.ToString();
        }

        public void setUpdatability()
        {

            if (isAggregated || isGrouped || isDistinctSelect || !isTopLevel)
            {
                return;
            }

            if (sortAndSlice.hasLimit() || sortAndSlice.hasOrder())
            {
                return;
            }

            if (rangeVariables.Length != 1)
            {
                return;
            }

            RangeVariable rangeVar = rangeVariables[0];
            Table table = rangeVar.getTable();
            Table baseTable = table.getBaseTable();

            _isInsertable = table.isInsertable();
            _isUpdatable = table.isUpdatable();

            if (!isInsertable() && !isUpdatable())
            {
                return;
            }

            IntValueHashMap columns = new IntValueHashMap();
            bool[] checkList;
            int[] baseColumnMap = table.getBaseTableColumnMap();
            int[] columnMap = new int[indexLimitVisible];

            for (int i = 0; i < indexLimitVisible; i++)
            {
                Expression expression = exprColumns[i];

                if (expression.getType() == OpTypes.COLUMN)
                {
                    String name = expression.getColumn().getName().name;

                    if (columns.containsKey(name))
                    {
                        columns.put(name, 1);

                        continue;
                    }

                    columns.put(name, 0);
                }
            }

            _isUpdatable = false;

            for (int i = 0; i < indexLimitVisible; i++)
            {
                if (accessibleColumns[i])
                {
                    Expression expression = exprColumns[i];

                    if (expression.getType() == OpTypes.COLUMN)
                    {
                        String name = expression.getColumn().getName().name;

                        if (columns.get(name) == 0)
                        {
                            int index = table.findColumn(name);

                            columnMap[i] = baseColumnMap[index];

                            if (columnMap[i] != -1)
                            {
                                _isUpdatable = true;
                            }

                            continue;
                        }
                    }
                }

                columnMap[i] = -1;
                _isInsertable = false;
            }

            if (_isInsertable)
            {
                checkList = baseTable.getColumnCheckList(columnMap);

                for (int i = 0; i < checkList.Length; i++)
                {
                    if (checkList[i])
                    {
                        continue;
                    }

                    ColumnSchema column = baseTable.getColumn(i);

                    if (column.isIdentity() || column.isGenerated()
                            || column.hasDefault() || column.isNullable()) { }
                    else
                    {
                        _isInsertable = false;

                        break;
                    }
                }
            }

            if (!_isUpdatable)
            {
                _isInsertable = false;
            }

            if (_isUpdatable)
            {
                this.columnMap = columnMap;
                this.baseTable = baseTable;

                if (persistenceScope == TableBase.SCOPE_STATEMENT)
                {
                    return;
                }

                indexLimitRowId++;

                indexLimitData = indexLimitRowId;
            }
        }

        public override Table getBaseTable()
        {
            return baseTable;
        }

        public override void collectAllExpressions(EfzList set, OrderedIntHashSet typeSet,
                                          OrderedIntHashSet stopAtTypeSet)
        {

            for (int i = 0; i < indexStartAggregates; i++)
            {
                Expression.collectAllExpressions(set, exprColumns[i], typeSet,
                                                 stopAtTypeSet);
            }

            Expression.collectAllExpressions(set, queryCondition, typeSet,
                                             stopAtTypeSet);
            Expression.collectAllExpressions(set, havingCondition, typeSet,
                                             stopAtTypeSet);
        }

        public override void collectObjectNames(Set set)
        {

            for (int i = 0; i < indexStartAggregates; i++)
            {
                exprColumns[i].collectObjectNames(set);
            }

            if (queryCondition != null)
            {
                queryCondition.collectObjectNames(set);
            }

            if (havingCondition != null)
            {
                havingCondition.collectObjectNames(set);
            }
        }

        void getMergedSelect() {

        RangeVariable rangeVar            = rangeVariables[0];
        Table         table               = rangeVar.getTable();
        Expression    localQueryCondition = queryCondition;
        Expression    baseQueryCondition  = null;

        if (table is TableDerived) {
            QuerySpecification baseSelect =
                ((TableDerived) table).queryExpression.getMainSelect();
            RangeVariable baseRangeVariable = baseSelect.rangeVariables[0];

            rangeVariables    = new RangeVariable[1];
            rangeVariables[0] = new RangeVariable(baseRangeVariable);

            Expression[] newExprColumns = new Expression[indexLimitRowId];

            for (int i = 0; i < indexLimitVisible; i++) {
                Expression e = exprColumns[i];

                newExprColumns[i] = e.replaceColumnReferences(rangeVar,
                        baseSelect.exprColumns);
            }

            exprColumns = newExprColumns;

            if (localQueryCondition != null) {
                localQueryCondition =
                    localQueryCondition.replaceColumnReferences(rangeVar,
                        baseSelect.exprColumns);
            }

            baseQueryCondition  = baseSelect.queryCondition;
            checkQueryCondition = baseSelect.checkQueryCondition;
        }

        queryCondition = ExpressionLogical.andExpressions(baseQueryCondition,
                localQueryCondition);

        if (queryCondition != null) {
            tempSet.clear();
            Expression.collectAllExpressions(tempSet, queryCondition,
                                             Expression.subqueryExpressionSet,
                                             Expression.emptyExpressionSet);

            int size = tempSet.size();

            for (int i = 0; i < size; i++) {
                Expression e = (Expression) tempSet.get(i);

                e.collectObjectNames(tempSet);
            }

            if (tempSet.contains(baseTable.getName())) {
                _isUpdatable  = false;
                _isInsertable = false;
            }
        }

        if (view != null) {
            switch (view.getCheckOption()) {

                case SchemaObjectViewCheckModes.CHECK_LOCAL :
                    if (!_isUpdatable) {
                        throw Error.error(ErrorCode.X_42537);
                    }

                    checkQueryCondition = localQueryCondition;
                    break;

                case SchemaObjectViewCheckModes.CHECK_CASCADE :
                    if (!_isUpdatable) {
                        throw Error.error(ErrorCode.X_42537);
                    }

                    checkQueryCondition = queryCondition;
                    break;
            }
        }

        setRangeVariableConditions();
    }

        /**
         * Not for views. Only used on root node.
         */
        public override void setAsTopLevel()
        {

            setReturningResultSet();

            acceptsSequences = true;
            isTopLevel = true;
        }

        public override void setReturningResultSet()
        {
            persistenceScope = TableBase.SCOPE_SESSION;
            columnMode = TableBase.COLUMNS_UNREFERENCED;
        }

        public override bool isSingleColumn()
        {
            return indexLimitVisible == 1;
        }

        public override String[] getColumnNames()
        {

            String[] names = new String[indexLimitVisible];

            for (int i = 0; i < indexLimitVisible; i++)
            {
                names[i] = exprColumns[i].getAlias();
            }

            return names;
        }

        public override SqlType[] getColumnTypes()
        {

            if (columnTypes.Length == indexLimitVisible)
            {
                return columnTypes;
            }

            SqlType[] types = new SqlType[indexLimitVisible];

            Array.Copy(columnTypes, types, types.Length);

            return types;
        }

        public override int getColumnCount()
        {
            return indexLimitVisible;
        }

        public override int[] getBaseTableColumnMap()
        {
            return columnMap;
        }

        public override Expression getCheckCondition()
        {
            return queryCondition;
        }

        public override void getBaseTableNames(OrderedHashSet set)
        {

            for (int i = 0; i < rangeVariables.Length; i++)
            {
                Table rangeTable = rangeVariables[i].rangeTable;
                QNameManager.QName name = rangeTable.getName();

                if (rangeTable.isReadOnly() || rangeTable.isTemp())
                {
                    continue;
                }

                if (name.schema == SqlInvariants.SYSTEM_SCHEMA_QNAME)
                {
                    continue;
                }

                set.add(name);
            }
        }
    }
}
